#pragma once
// -------------------------------------------------
// ZED Kit
// -------------------------------------------------
//   File Name: cf_wrappers.hpp
//      Author: Ziming Li
//     Created: 2022-09-22
// -------------------------------------------------
// Copyright (C) 2022 MingYang Software Technology.
// -------------------------------------------------

#ifndef ZED_APPLE_CF_WRAPPERS_HPP
#define ZED_APPLE_CF_WRAPPERS_HPP

#include <string>
#include <CoreFoundation/CoreFoundation.h>
#include "../memory.hpp"

namespace zed {

struct cf_type_ref_finalizer {
    void operator()(CFTypeRef cf) const { ::CFRelease(cf); }
};

template <class T>
class cf_type_ref : public unique_resource<T, cf_type_ref_finalizer>
{
protected:
    cf_type_ref(T cf) : unique_resource<T, cf_type_ref_finalizer>(cf) {}
};

class cf_dictionary_ref : public cf_type_ref<CFDictionaryRef>
{
public:
    explicit cf_dictionary_ref(CFDictionaryRef cfd) : cf_type_ref<CFDictionaryRef>(cfd) {}

    bool get_value(CFStringRef key, int &dst) const;
    bool get_value(CFStringRef key, std::string &dst) const;
private:
    template <typename T>
    bool get_number_value(CFStringRef key, CFNumberType type, T &dst) const;
};

class cf_string_ref : public cf_type_ref<CFStringRef>
{
public:
    explicit cf_string_ref(const char *psz, bool no_copy = false);
private:
    static CFStringRef create_with_cstring(const char *psz, bool no_copy);
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementations

template <typename T>
bool cf_dictionary_ref::get_number_value(CFStringRef key, CFNumberType type, T &dst) const
{
    CFTypeRef v = ::CFDictionaryGetValue(get(), key);
    if (nullptr == v || ::CFNumberGetTypeID() != ::CFGetTypeID(v))
        return false;
    return ::CFNumberGetValue(reinterpret_cast<CFNumberRef>(v), type, &dst);
}

inline bool cf_dictionary_ref::get_value(CFStringRef key, int &dst) const
{
    return get_number_value(key, kCFNumberIntType, dst);
}

inline bool cf_dictionary_ref::get_value(CFStringRef key, std::string &dst) const
{
    CFTypeRef v = ::CFDictionaryGetValue(get(), key);
    if (nullptr == v || ::CFStringGetTypeID() != ::CFGetTypeID(v))
        return false;

    CFStringRef s = reinterpret_cast<CFStringRef>(v);
    CFRange range = ::CFRangeMake(0, CFStringGetLength(s));

    UInt8 buf[128];
    while (range.length > 0)
    {
        CFIndex used;
        CFIndex cch = ::CFStringGetBytes(s, range, kCFStringEncodingUTF8, '?', false, buf, sizeof(buf), &used);
        if (0 == cch)
            break;

        dst.append(reinterpret_cast<const char *>(buf), used);
        range.location += cch;
        range.length -= cch;
    }

    return true;
}

cf_string_ref::cf_string_ref(const char *psz, bool no_copy) : cf_type_ref<CFStringRef>(create_with_cstring(psz, no_copy))
{
}

CFStringRef cf_string_ref::create_with_cstring(const char *psz, bool no_copy)
{
    return no_copy
        ? ::CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, psz, kCFStringEncodingUTF8, kCFAllocatorNull)
        : ::CFStringCreateWithCString(kCFAllocatorDefault, psz, kCFStringEncodingUTF8);
}

} // namespace zed

#endif // ZED_APPLE_CF_WRAPPERS_HPP
